﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using DACU.Properties;

namespace DACU.Tools.JavaScript
{
	public class JavaScriptSerializer
	{
		// Fields
		private Dictionary<Type, JavaScriptConverter> _converters;
		private int _maxJsonLength;
		private int _recursionLimit;
		private JavaScriptTypeResolver _typeResolver;
		internal static readonly long DatetimeMinTimeTicks;
		internal const int DefaultMaxJsonLength = 0x200000;
		internal const int DefaultRecursionLimit = 100;
		internal const string ServerTypeFieldName = "__type";

		// Methods
		static JavaScriptSerializer()
		{
			var time = new DateTime(0x7b2, 1, 1, 0, 0, 0, DateTimeKind.Utc);
			DatetimeMinTimeTicks = time.Ticks;
		}

		public JavaScriptSerializer() : this(null)
		{
		}

		public JavaScriptSerializer(JavaScriptTypeResolver resolver)
		{
			_typeResolver = resolver;
			RecursionLimit = 100;
			MaxJsonLength = 0x200000;
		}

		internal bool ConverterExistsForType(Type t, out JavaScriptConverter converter)
		{
			converter = GetConverter(t);
			return (converter != null);
		}

		public T ConvertToType<T>(object obj)
		{
			return (T) ObjectConverter.ConvertObjectToType(obj, typeof(T), this);
		}

		public T Deserialize<T>(string input)
		{
			return (T) Deserialize(this, input, typeof(T), RecursionLimit);
		}

		internal static object Deserialize(JavaScriptSerializer serializer, string input, Type type, int depthLimit)
		{
			if (input == null)
			{
				throw new ArgumentNullException("input");
			}
			if (input.Length > serializer.MaxJsonLength)
			{
				throw new ArgumentException(Resources.JSON_MaxJsonLengthExceeded, "input");
			}
			return ObjectConverter.ConvertObjectToType(JavaScriptObjectDeserializer.BasicDeserialize(input, depthLimit, serializer), type, serializer);
		}

		public object DeserializeObject(string input)
		{
			return Deserialize(this, input, null, RecursionLimit);
		}

		private JavaScriptConverter GetConverter(Type t)
		{
			if (_converters != null)
			{
				while (t != null)
				{
					if (_converters.ContainsKey(t))
						return _converters[t];
					t = t.BaseType;
				}
			}
			return null;
		}

		public void RegisterConverters(IEnumerable<JavaScriptConverter> converters)
		{
			if (converters == null)
				throw new ArgumentNullException("converters");
			foreach (JavaScriptConverter converter in converters)
			{
				IEnumerable<Type> supportedTypes = converter.SupportedTypes;
				if (supportedTypes != null)
				{
					foreach (Type type in supportedTypes)
						Converters[type] = converter;
					continue;
				}
			}
		}

		public string Serialize(object obj)
		{
			return Serialize(obj, SerializationFormat.JSON);
		}

		public void Serialize(object obj, StringBuilder output)
		{
			Serialize(obj, output, SerializationFormat.JSON);
		}

		private string Serialize(object obj, SerializationFormat serializationFormat)
		{
			var output = new StringBuilder();
			Serialize(obj, output, serializationFormat);
			return output.ToString();
		}

		internal void Serialize(object obj, StringBuilder output, SerializationFormat serializationFormat)
		{
			SerializeValue(obj, output, 0, null, serializationFormat);
			if ((serializationFormat == SerializationFormat.JSON) && (output.Length > MaxJsonLength))
			{
				throw new InvalidOperationException(Resources.JSON_MaxJsonLengthExceeded);
			}
		}

		private static void SerializeBoolean(bool o, StringBuilder sb)
		{
			sb.Append(o ? "true" : "false");
		}

		private void SerializeCustomObject(object o, StringBuilder sb, int depth, Hashtable objectsInUse, SerializationFormat serializationFormat)
		{
			bool flag = true;
			Type type = o.GetType();
			sb.Append('{');
			if (TypeResolver != null)
			{
				string str = TypeResolver.ResolveTypeId(type);
				if (str != null)
				{
					SerializeString("__type", sb);
					sb.Append(':');
					SerializeValue(str, sb, depth, objectsInUse, serializationFormat);
					flag = false;
				}
			}
			foreach (FieldInfo info in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
			{
				if (!info.IsDefined(typeof(ScriptIgnoreAttribute), true))
				{
					if (!flag)
						sb.Append(',');
					SerializeString(info.Name, sb);
					sb.Append(':');
					SerializeValue(info.GetValue(o), sb, depth, objectsInUse, serializationFormat);
					flag = false;
				}
			}
			foreach (PropertyInfo info2 in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
			{
				if (!info2.IsDefined(typeof(ScriptIgnoreAttribute), true))
				{
					MethodInfo getMethod = info2.GetGetMethod();
					if ((getMethod != null) && (getMethod.GetParameters().Length <= 0))
					{
						if (!flag)
							sb.Append(',');
						SerializeString(info2.Name, sb);
						sb.Append(':');
						SerializeValue(getMethod.Invoke(o, null), sb, depth, objectsInUse, serializationFormat);
						flag = false;
					}
				}
			}
			sb.Append('}');
		}

		private static void SerializeDateTime(DateTime datetime, StringBuilder sb, SerializationFormat serializationFormat)
		{
			if (serializationFormat == SerializationFormat.JSON)
			{
				sb.Append("\"\\/Date(");
				sb.Append((long) ((datetime.ToUniversalTime().Ticks - DatetimeMinTimeTicks) / 0x2710L));
				sb.Append(")\\/\"");
			}
			else
			{
				sb.Append("new Date(");
				sb.Append((long) ((datetime.ToUniversalTime().Ticks - DatetimeMinTimeTicks) / 0x2710L));
				sb.Append(")");
			}
		}

		private void SerializeDictionary(IDictionary o, StringBuilder sb, int depth, Hashtable objectsInUse, SerializationFormat serializationFormat)
		{
			sb.Append('{');
			bool flag = true;
			bool flag2 = false;
			if (o.Contains("__type"))
			{
				flag = false;
				flag2 = true;
				SerializeDictionaryKeyValue("__type", o["__type"], sb, depth, objectsInUse, serializationFormat);
			}
			foreach (DictionaryEntry entry in o)
			{
				var key = entry.Key as string;
				if (key == null)
					throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.JSON_DictionaryTypeNotSupported, new object[] {o.GetType().FullName}));
				if (flag2 && string.Equals(key, "__type", StringComparison.Ordinal))
					flag2 = false;
				else
				{
					if (!flag)
						sb.Append(',');
					SerializeDictionaryKeyValue(key, entry.Value, sb, depth, objectsInUse, serializationFormat);
					flag = false;
				}
			}
			sb.Append('}');
		}

		private void SerializeDictionaryKeyValue(string key, object value, StringBuilder sb, int depth, Hashtable objectsInUse, SerializationFormat serializationFormat)
		{
			SerializeString(key, sb);
			sb.Append(':');
			SerializeValue(value, sb, depth, objectsInUse, serializationFormat);
		}

		private void SerializeEnumerable(IEnumerable enumerable, StringBuilder sb, int depth, Hashtable objectsInUse, SerializationFormat serializationFormat)
		{
			sb.Append('[');
			bool flag = true;
			foreach (object obj2 in enumerable)
			{
				if (!flag)
					sb.Append(',');
				SerializeValue(obj2, sb, depth, objectsInUse, serializationFormat);
				flag = false;
			}
			sb.Append(']');
		}

		private static void SerializeGuid(Guid guid, StringBuilder sb)
		{
			sb.Append("\"").Append(guid.ToString()).Append("\"");
		}

		internal static string SerializeInternal(object o)
		{
			var serializer = new JavaScriptSerializer();
			return serializer.Serialize(o);
		}

		private static void SerializeString(string input, StringBuilder sb)
		{
			sb.Append('"');
			sb.Append(JavaScriptString.QuoteString(input));
			sb.Append('"');
		}

		private static void SerializeUri(Uri uri, StringBuilder sb)
		{
			sb.Append("\"").Append(uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped)).Append("\"");
		}

		private void SerializeValue(object o, StringBuilder sb, int depth, Hashtable objectsInUse, SerializationFormat serializationFormat)
		{
			if (++depth > _recursionLimit)
			{
				throw new ArgumentException(Resources.JSON_DepthLimitExceeded);
			}
			JavaScriptConverter converter = null;
			if ((o != null) && ConverterExistsForType(o.GetType(), out converter))
			{
				IDictionary<string, object> dictionary = converter.Serialize(o, this);
				if (TypeResolver != null)
				{
					string str = TypeResolver.ResolveTypeId(o.GetType());
					if (str != null)
						dictionary["__type"] = str;
				}
				sb.Append(Serialize(dictionary, serializationFormat));
			}
			else
				SerializeValueInternal(o, sb, depth, objectsInUse, serializationFormat);
		}

		private void SerializeValueInternal(object o, StringBuilder sb, int depth, Hashtable objectsInUse, SerializationFormat serializationFormat)
		{
			if ((o == null) || DBNull.Value.Equals(o))
				sb.Append("null");
			else
			{
				var input = o as string;
				if (input != null)
					SerializeString(input, sb);
				else if (o is char)
				{
					if (((char) o) == '\0')
						sb.Append("null");
					else
						SerializeString(o.ToString(), sb);
				}
				else
					if (o is bool)
						SerializeBoolean((bool) o, sb);
					else
						if (o is DateTime)
							SerializeDateTime((DateTime) o, sb, serializationFormat);
						else
							if (o is Guid)
								SerializeGuid((Guid) o, sb);
							else
							{
								var uri = o as Uri;
								if (uri != null)
									SerializeUri(uri, sb);
								else
									if (o is double)
										sb.Append(((double) o).ToString("r", CultureInfo.InvariantCulture));
									else
										if (o is float)
											sb.Append(((float) o).ToString("r", CultureInfo.InvariantCulture));
										else
											if (o.GetType().IsPrimitive || (o is decimal))
											{
												var convertible = o as IConvertible;
												sb.Append(convertible != null ? convertible.ToString(CultureInfo.InvariantCulture) : o.ToString());
											}
											else
											{
												Type enumType = o.GetType();
												if (enumType.IsEnum)
												{
													Type underlyingType = Enum.GetUnderlyingType(enumType);
													if ((underlyingType == typeof (long)) || (underlyingType == typeof (ulong)))
														throw new InvalidOperationException(Resources.JSON_InvalidEnumType);
													sb.Append(((Enum) o).ToString("D"));
												}
												else
												{
													try
													{
														if (objectsInUse == null)
															objectsInUse = new Hashtable(new ReferenceComparer());
														else
															if (objectsInUse.ContainsKey(o))
																throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.JSON_CircularReference,new object[] {enumType.FullName}));
														objectsInUse.Add(o, null);
														var dictionary = o as IDictionary;
														if (dictionary != null)
															SerializeDictionary(dictionary, sb, depth, objectsInUse, serializationFormat);
														else
														{
															var enumerable = o as IEnumerable;
															if (enumerable != null)
																SerializeEnumerable(enumerable, sb, depth, objectsInUse, serializationFormat);
															else
																SerializeCustomObject(o, sb, depth, objectsInUse, serializationFormat);
														}
													}
													finally
													{
														if (objectsInUse != null)
															objectsInUse.Remove(o);
													}
												}
											}
							}
			}
		}

		// Properties
		private Dictionary<Type, JavaScriptConverter> Converters
		{
			get
			{
				if (_converters == null)
					_converters = new Dictionary<Type, JavaScriptConverter>();
				return _converters;
			}
		}

		public int MaxJsonLength
		{
			get
			{
				return _maxJsonLength;
			}
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException(Resources.JSON_InvalidMaxJsonLength);
				_maxJsonLength = value;
			}
		}

		public int RecursionLimit
		{
			get
			{
				return _recursionLimit;
			}
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException(Resources.JSON_InvalidRecursionLimit);
				_recursionLimit = value;
			}
		}

		internal JavaScriptTypeResolver TypeResolver
		{
			get
			{
				return _typeResolver;
			}
		}

		// Nested Types
		private class ReferenceComparer : IEqualityComparer
		{
			// Methods
			bool IEqualityComparer.Equals(object x, object y)
			{
				return (x == y);
			}

			int IEqualityComparer.GetHashCode(object obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		internal enum SerializationFormat
		{
			JSON,
			JavaScript
		}
	}
}
